﻿using Puppy.ActorDb.Query;
using Puppy.Domain.Entities;
using Puppy.Domain.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace Puppy.ActorDb
{
    public class ActorDbRepository<TActorDbContext, TEntity> :
        RepositoryBase<TEntity>, IActorDbRepository<TEntity>
        where TActorDbContext : ActorDbContext
        where TEntity : class, IEntity
    {
        // todo: add event and audit

        protected IActorDatabaseProvider<TActorDbContext> DatabaseProvider { get; }

        protected IActorQueryable<TEntity> ActorQueryable { get; }

        public ActorDbRepository(IActorDatabaseProvider<TActorDbContext> databaseProvider)
        {
            DatabaseProvider = databaseProvider;
        }

        public Task<IActorDatabase> GetDatabaseAsync()
        {
            return DatabaseProvider.GetDatabaseAsync();
        }

        public async Task<IActorSet<TEntity>> GetCollectionAsync()
        {
            return (await GetDatabaseAsync()).Collection<TEntity>();
        }

        public override Task<IQueryable<TEntity>> GetQueryableAsync()
        {
            return Task.FromResult(new ActorQueryable<TEntity>(null, null).AsQueryable());
            // return ApplyDataFilters((await GetCollectionAsync()));
        }

        public override async Task<TEntity> FindAsync(
            Expression<Func<TEntity, bool>> predicate,
            bool includeDetails = true,
            CancellationToken cancellationToken = default)
        {
            return (await GetQueryableAsync()).Where(predicate).SingleOrDefault();
        }

        public override async Task DeleteAsync(
            Expression<Func<TEntity, bool>> predicate,
            bool autoSave = false,
            CancellationToken cancellationToken = default)
        {
            var entities = (await GetQueryableAsync()).Where(predicate).ToList();

            await DeleteManyAsync(entities, autoSave, cancellationToken);
        }

        public override async Task DeleteAsync(
            TEntity entity,
            bool autoSave = false,
            CancellationToken cancellationToken = default)
        {
            // todo softdelete
            await (await GetCollectionAsync()).RemoveAsync(entity);
        }

        public override async Task<long> GetCountAsync(CancellationToken cancellationToken = default)
        {
            // return await AsyncExecuter.LongCountAsync(await GetQueryableAsync());
            return (await GetQueryableAsync()).LongCount();
        }

        public override async Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            return (await GetQueryableAsync()).ToList();
        }

        public override async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            return (await GetQueryableAsync()).Where(predicate).ToList();
        }

        public override async Task<List<TEntity>> GetPagedListAsync(
            int skipCount,
            int maxResultCount,
            string sorting,
            bool includeDetails = false,
            CancellationToken cancellationToken = default)
        {
            return (await GetQueryableAsync())
                .OrderBy(sorting)
                .PageBy(skipCount, maxResultCount)
                .ToList();
        }

        public override async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            await (await GetCollectionAsync()).AddOrUpdateAsync(entity);

            return entity;
        }

        public override async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            await (await GetCollectionAsync()).AddOrUpdateAsync(entity);

            return entity;
        }
    }

    public class ActorDbRepository<TActorDbContext, TEntity, TKey> : ActorDbRepository<TActorDbContext, TEntity>, IActorDbRepository<TEntity, TKey>
        where TActorDbContext : ActorDbContext
        where TEntity : class, IEntity<TKey>
    {
        public ActorDbRepository(IActorDatabaseProvider<TActorDbContext> databaseProvider)
            : base(databaseProvider)
        {
        }

        public override async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            await SetIdIfNeededAsync(entity);
            return await base.InsertAsync(entity, autoSave, cancellationToken);
        }

        protected virtual async Task SetIdIfNeededAsync(TEntity entity)
        {
            if (typeof(TKey) == typeof(int) ||
                typeof(TKey) == typeof(long) ||
                typeof(TKey) == typeof(Guid))
            {
                if (EntityHelper.HasDefaultId(entity))
                {
                    var nextId = (await GetDatabaseAsync()).GenerateNextId<TEntity, TKey>();
                    EntityHelper.TrySetId(entity, () => nextId);
                }
            }
        }

        public virtual async Task<TEntity> GetAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            var entity = await FindAsync(id, includeDetails, cancellationToken);

            if (entity == null)
            {
                throw new EntityNotFoundException(typeof(TEntity), id);
            }

            return entity;
        }

        public virtual async Task<TEntity> FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            return (await GetQueryableAsync()).FirstOrDefault(e => e.Id.Equals(id));
        }

        public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            await DeleteAsync(x => x.Id.Equals(id), autoSave, cancellationToken);
        }

        public virtual async Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var entities = await AsyncExecuter.ToListAsync((await GetQueryableAsync()).Where(x => ids.Contains(x.Id)), cancellationToken);
            await DeleteManyAsync(entities, autoSave, cancellationToken);
        }
    }
}
